package com.xingchi.tornado.dict.config;

import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import com.google.common.base.CaseFormat;
import com.xingchi.tornado.basic.BaseEnum;
import com.xingchi.tornado.core.scanner.BeanMetadata;
import com.xingchi.tornado.core.scanner.ClassPathComponentScanner;
import com.xingchi.tornado.dict.anno.Dict;
import com.xingchi.tornado.dict.model.DictMetadata;
import com.xingchi.tornado.dict.service.DictService;
import com.xingchi.tornado.dict.service.DictStorageService;
import com.xingchi.tornado.dict.service.InMemoryDictStorageService;
import com.xingchi.tornado.dict.service.JdbcDictStorageService;
import com.xingchi.tornado.dict.service.RedisDictStorageService;
import com.xingchi.tornado.utils.StopWatchWrap;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.spring.annotation.MapperScan;
import org.redisson.spring.starter.RedissonAutoConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.core.RedisTemplate;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.xingchi.tornado.dict.config.DictProperties.DICT_REFRESH;

/**
 * 字典自动配置类
 *
 * @author xingchi
 * @date 2024/5/19 20:29
 */
@Configuration
@ComponentScan(basePackages = "com.xingchi.tornado.dict")
@MapperScan(basePackages = {"com.xingchi.tornado.dict.mapper"})
@AutoConfigureAfter({MybatisPlusAutoConfiguration.class, RedissonAutoConfiguration.class})
public class DictAutoConfiguration {

    @Bean
    @ConditionalOnProperty(name = DictProperties.DICT_SERVICE_STORAGE_TYPE, havingValue = "in_memory")
    public DictStorageService memoryDictStorageService() {
        return new InMemoryDictStorageService();
    }

    @Bean
    @ConditionalOnProperty(name = DictProperties.DICT_SERVICE_STORAGE_TYPE, havingValue = "jdbc")
    public DictStorageService jdbcDictStorageService(DictService dictService) {
        return new JdbcDictStorageService(dictService);
    }

    @Bean
    @ConditionalOnProperty(name = DictProperties.DICT_SERVICE_STORAGE_TYPE, havingValue = "redis")
    public DictStorageService redisDictStorageService(RedisTemplate<String, List<DictMetadata.Item>> redisTemplate) {
        return new RedisDictStorageService(redisTemplate);
    }

    @Bean
    @ConditionalOnMissingBean
    public DictStorageService defaultDictStorageService() {
        return new InMemoryDictStorageService();
    }

    @Slf4j
    @Configuration
    @EnableConfigurationProperties(DictProperties.class)
    @ConditionalOnProperty(name = DICT_REFRESH, havingValue = "true")
    static class DictLoader implements ApplicationListener<ApplicationStartedEvent> {

        @Autowired
        private DictProperties dictProperties;
        @Autowired
        private DictStorageService dictStorageService;

        @Override
        public void onApplicationEvent(ApplicationStartedEvent event) {

            StopWatchWrap watch = StopWatchWrap.watch("字典数据刷新");
            watch.start();
            log.info("The dictionary data refresh starts!");

            List<DictMetadata> dictMetadataList = obtainDictMetadataList();
            dictStorageService.storage(dictMetadataList);
            watch.stop();
            log.info("Dictionary data is refreshed successfully!");
            log.info("Refreshing dictionary data takes time: {}", watch.getTotalTimeSeconds());
        }

        private List<DictMetadata> obtainDictMetadataList() {

            List<DictMetadata> result = new ArrayList<>();
            String basePackage = dictProperties.getBasePackage();
            Set<BeanMetadata> components = new HashSet<>();
            try {
                components = ClassPathComponentScanner.findComponents(basePackage, BaseEnum.class);
            } catch (IOException e) {
                log.error("Dictionary data refresh failed! cause: {}", e.getMessage(), e);
            }
            String ignoreSuffix = "_" + dictProperties.getIgnoreSuffix().toUpperCase();
            for (BeanMetadata component : components) {

                DictMetadata dictMetadata = new DictMetadata();
                dictMetadata.setClassName(component.getBeanClassName());
                Class<?> targetClass = component.getTargetClass();

                Dict dict = AnnotationUtils.getAnnotation(targetClass, Dict.class);
                if (dict != null) {
                    dictMetadata.setGroup(dict.group());
                } else {
                    String simpleName = targetClass.getSimpleName();
                    String group = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, simpleName);
                    if (group.endsWith(ignoreSuffix)) {
                        group = StringUtils.substring(group, 0, group.length() - ignoreSuffix.length());
                    }
                    dictMetadata.setGroup(group);
                }

                if (targetClass.isEnum() && BaseEnum.class.isAssignableFrom(targetClass)) {
                    dictMetadata.setTargetClass((Class<? extends BaseEnum>) targetClass);

                    BaseEnum[] dictItems = (BaseEnum[]) targetClass.getEnumConstants();
                    List<DictMetadata.Item> items = new ArrayList<>();
                    for (BaseEnum dictItem : dictItems) {
                        DictMetadata.Item item = new DictMetadata.Item();
                        item.setCode(dictItem.getCode());
                        item.setDesc(dictItem.getDescription());
                        item.setOrder(((Enum<?>) dictItem).ordinal());
                        item.setName(((Enum<?>) dictItem).name());
                        item.setVisible(dictItem.isVisible());
                        items.add(item);
                    }
                    Stream<DictMetadata.Item> stream = items.stream().sorted(Comparator.comparing(DictMetadata.Item::getOrder));
                    dictMetadata.setItems(stream.collect(Collectors.toList()));
                }
                result.add(dictMetadata);
            }
            return result;
        }

    }

}
